//
//  NoteReviewController.swift
//  hello
//
//  Created by 赵康 on 2025/3/30.
//

// NoteReviewController.swift

import Fluent
import Queues
import Redis
import Vapor

struct NoteReviewController: RouteCollection {
  func boot(routes: any RoutesBuilder) throws {
    let reviews = routes.grouped("\(RouteEnum.noteReviews.rawValue)")
    let protected = reviews.grouped(AuthMiddleware())

    // 复习设置相关路由
    protected.get("\(RouteEnum.settings.rawValue)", use: getReviewSettings)
    protected.put("\(RouteEnum.settings.rawValue)", use: updateReviewSettings)

    // 复习数据相关路由
    protected.get("\(RouteEnum.todayReviews.rawValue)", use: getTodayReviews)
    protected.post("\(RouteEnum.updateReviewStatus.rawValue)", ":noteID", use: updateReviewStatus)

    // 同步相关路由
    protected.post("\(RouteEnum.syncReviewData.rawValue)", use: syncReviewData)
    // 统计相关路由
    protected.get("\(RouteEnum.reviewStats.rawValue)", use: getReviewStats)

    protected.post("\(RouteEnum.addToReview.rawValue)", use: addNotesToReview)
    protected.post("\(RouteEnum.removeFromReview.rawValue)", use: removeNotesFromReview)

    protected.post("\(RouteEnum.toggleSuspendStatus.rawValue)", ":noteID", use: toggleSuspendStatus)

    protected.get("\(RouteEnum.reviewButtonStats.rawValue)", use: getReviewButtonStats)

    // 添加复习量预测路由
    protected.get("\(RouteEnum.reviewPrediction.rawValue)", use: getReviewPrediction)
  }

  // 获取用户复习设置
  @Sendable
  func getReviewSettings(req: Request) async throws -> UserReviewSettings.Response {
    let userID = try req.authenticatedUserID

    // 尝试从缓存获取
    let cacheKey = CacheConfig.KeyBuilder.userReviewSettings(userID: userID)
    if let cachedSettings = try await req.cache.get(
      forKey: cacheKey,
      as: UserReviewSettings.Response.self,
      from: req.redis
    ) {
      return cachedSettings
    }

    // 查询数据库
    let settings =
      try await UserReviewSettings.query(on: req.db)
      .filter(\.$user.$id == userID)
      .first()
      ?? UserReviewSettings(userID: userID)  // 如果不存在则创建默认设置

    // 如果是新创建的设置，保存到数据库
    if settings.id == nil {
      try await settings.save(on: req.db)
    }

    let response = UserReviewSettings.Response(
      id: settings.id,
      userID: userID,
      dailyReviewCount: settings.dailyReviewCount,
      prioritizeRecent: settings.prioritizeRecent,
      lastSyncDate: settings.lastSyncDate,

      // Anki算法设置
      newCardsPerDay: settings.newCardsPerDay,
      learningSteps: settings.learningSteps,
      maxReviewsPerDay: settings.maxReviewsPerDay,
      intervalModifier: settings.intervalModifier,
      maxInterval: settings.maxInterval,
      lapseSteps: settings.lapseSteps,
      lapseNewInterval: settings.lapseNewInterval,
      leechThreshold: settings.leechThreshold,

      createdAt: settings.createdAt,
      updatedAt: settings.updatedAt
    )

    // 缓存响应
    try await req.cache.set(
      response,
      forKey: cacheKey,
      duration: CacheConfig.Duration.medium,
      type: .userSettings,
      on: req.redis
    )

    return response
  }

  // 更新用户复习设置
  @Sendable
  func updateReviewSettings(req: Request) async throws -> UserReviewSettings.Response {
    let userID = try req.authenticatedUserID
    let updateRequest = try req.content.decode(UserReviewSettings.Update.self)

    // 获取当前设置或创建新设置
    let settings =
      try await UserReviewSettings.query(on: req.db)
      .filter(\.$user.$id == userID)
      .first()
      ?? UserReviewSettings(userID: userID)

    // 更新基本设置
    if let dailyReviewCount = updateRequest.dailyReviewCount {
      settings.dailyReviewCount = max(1, min(100, dailyReviewCount))  // 限制范围
    }

    if let prioritizeRecent = updateRequest.prioritizeRecent {
      settings.prioritizeRecent = prioritizeRecent
    }

    // 更新Anki算法设置
    if let newCardsPerDay = updateRequest.newCardsPerDay {
      settings.newCardsPerDay = max(1, min(1000, newCardsPerDay))
    }

    if let learningSteps = updateRequest.learningSteps {
      settings.learningSteps = learningSteps.isEmpty ? [1, 10] : learningSteps
    }

    if let maxReviewsPerDay = updateRequest.maxReviewsPerDay {
      settings.maxReviewsPerDay = max(1, maxReviewsPerDay)
    }

    if let intervalModifier = updateRequest.intervalModifier {
      settings.intervalModifier = max(0.1, min(5.0, intervalModifier))
    }

    if let maxInterval = updateRequest.maxInterval {
      settings.maxInterval = max(1, maxInterval)
    }

    if let lapseSteps = updateRequest.lapseSteps {
      settings.lapseSteps = lapseSteps.isEmpty ? [10] : lapseSteps
    }

    if let lapseNewInterval = updateRequest.lapseNewInterval {
      settings.lapseNewInterval = max(0.1, min(1.0, lapseNewInterval))
    }

    if let leechThreshold = updateRequest.leechThreshold {
      settings.leechThreshold = max(1, leechThreshold)
    }

    settings.updatedAt = Date()

    // 保存到数据库
    try await settings.save(on: req.db)

    // 清除缓存
    let cacheKey = CacheConfig.KeyBuilder.userReviewSettings(userID: userID)
    try await req.cache.delete(forKey: cacheKey, on: req.redis)

    // 同时清除今日复习列表缓存
    let todayReviewsCacheKey = CacheConfig.KeyBuilder.todayReviews(userID: userID)
    try await req.cache.delete(forKey: todayReviewsCacheKey, on: req.redis)

    return UserReviewSettings.Response(
      id: settings.id,
      userID: userID,
      dailyReviewCount: settings.dailyReviewCount,
      prioritizeRecent: settings.prioritizeRecent,
      lastSyncDate: settings.lastSyncDate,

      // Anki算法设置
      newCardsPerDay: settings.newCardsPerDay,
      learningSteps: settings.learningSteps,
      maxReviewsPerDay: settings.maxReviewsPerDay,
      intervalModifier: settings.intervalModifier,
      maxInterval: settings.maxInterval,
      lapseSteps: settings.lapseSteps,
      lapseNewInterval: settings.lapseNewInterval,
      leechThreshold: settings.leechThreshold,

      createdAt: settings.createdAt,
      updatedAt: settings.updatedAt
    )
  }

  // 获取今日需要复习的笔记
  // 获取今日需要复习的笔记
  @Sendable
  func getTodayReviews(req: Request) async throws -> [NoteReview.ReviewNoteResponse] {
    let userID = try req.authenticatedUserID

    // 尝试从缓存获取
    let cacheKey = CacheConfig.KeyBuilder.todayReviews(userID: userID)
    if let cachedReviews = try await req.cache.get(
      forKey: cacheKey,
      as: [NoteReview.ReviewNoteResponse].self,
      from: req.redis
    ) {
      return cachedReviews
    }

    // 获取用户设置
    let settings =
      try await UserReviewSettings.query(on: req.db)
      .filter(\.$user.$id == userID)
      .first()
      ?? UserReviewSettings(userID: userID)

    let now = Date()

    // 查询今日需要复习的笔记
    let reviewsQuery = NoteReview.query(on: req.db)
      .filter(\.$user.$id == userID)
      .filter(\.$nextReviewDate <= now)
      .filter(\.$difficulty != .ignored)  // 排除不再复习的笔记
      .filter(\.$learningStage != .suspended)  // 排除暂停的笔记
      .with(\.$note) { note in
        note.with(\.$audio) { audio in
          audio.with(\.$album)
        }
        note.with(\.$user)
      }

    // 根据设置决定排序方式
    if settings.prioritizeRecent {
      reviewsQuery.sort(\NoteReview.$note.$id, .descending)
    } else {
      // 按照复习日期排序
      reviewsQuery.sort(\NoteReview.$nextReviewDate, .ascending)
    }

    // 限制复习数量
    reviewsQuery.limit(settings.maxReviewsPerDay)

    var reviews = try await reviewsQuery.all()

    // 如果设置为随机排序且不优先最近添加的
    if !settings.prioritizeRecent {
      reviews.shuffle()
    }

    // 构建笔记响应
    let responses = reviews.map { review -> NoteReview.ReviewNoteResponse in
      let note = review.note

      // 计算不同难度的下次复习时间
      let againResult = NoteReviewService.calculateNextReview(
        review: review,
        difficulty: .again,
        settings: settings
      )

      let hardResult = NoteReviewService.calculateNextReview(
        review: review,
        difficulty: .hard,
        settings: settings
      )

      let goodResult = NoteReviewService.calculateNextReview(
        review: review,
        difficulty: .good,
        settings: settings
      )

      let easyResult = NoteReviewService.calculateNextReview(
        review: review,
        difficulty: .easy,
        settings: settings
      )

      let nextReviewDates = NoteReview.ReviewNoteResponse.NextReviewDates(
        again: againResult.nextDate,
        hard: hardResult.nextDate,
        good: goodResult.nextDate,
        easy: easyResult.nextDate
      )

      let currentReview = NoteReview.ReviewNoteResponse.ReviewInfo(
        difficulty: review.difficulty,
        learningStage: review.learningStage,
        interval: review.interval,
        easeFactor: review.easeFactor,
        reviewCount: review.reviewCount,
        lapseCount: review.lapseCount
      )

      return NoteReview.ReviewNoteResponse(
        id: note.id,
        content: note.content,
        timestamp: note.timestamp ?? 0,
        tags: note.tags ?? [],
        userID: note.$user.id,
        audioID: note.$audio.id,
        answer: note.answer,
        isArabic: note.isArabic,
        isAnswerArabic: note.isAnswerArabic,
        createdAt: note.createdAt,
        updatedAt: note.updatedAt,
        nextReviewDates: nextReviewDates,
        currentReview: currentReview
      )
    }

    // 缓存响应 (短时间缓存，因为复习状态可能频繁变化)
    try await req.cache.set(
      responses,
      forKey: cacheKey,
      duration: CacheConfig.Duration.ultraShort,
      type: .note,
      on: req.redis
    )

    return responses
  }

  // 更新笔记复习状态
  @Sendable
  func updateReviewStatus(req: Request) async throws -> BasicResponse {
    let userID = try req.authenticatedUserID

    guard let noteIDString = req.parameters.get("noteID") else {
      throw Abort(.badRequest, reason: "无效的笔记ID参数")
    }

    guard let noteID = UUID(noteIDString) else {
      throw Abort(.badRequest, reason: "笔记ID格式无效")
    }

    let updateRequest = try req.content.decode(NoteReview.Update.self)

    // 验证笔记存在
    guard let note = try await Note.find(noteID, on: req.db) else {
      throw Abort(.notFound, reason: "笔记不存在")
    }

    // 获取用户设置
    let settings =
      try await UserReviewSettings.query(on: req.db)
      .filter(\.$user.$id == userID)
      .first()
      ?? UserReviewSettings(userID: userID)

    // 查找或创建复习记录
    let review =
      try await NoteReview.query(on: req.db)
      .filter(\.$note.$id == noteID)
      .filter(\.$user.$id == userID)
      .first()
      ?? NoteReview(
        noteID: noteID,
        userID: userID,
        easeFactor: 2.5,
        interval: 0,
        learningStage: .new,
        lapseCount: 0
      )

    // 更新复习状态
    review.difficulty = updateRequest.difficulty
    review.lastReviewDate = Date()
    review.reviewCount += 1

    // 使用Anki算法计算下次复习日期和状态
    let result = NoteReviewService.calculateNextReview(
      review: review,
      difficulty: updateRequest.difficulty,
      settings: settings
    )

    review.nextReviewDate = result.nextDate
    review.easeFactor = result.easeFactor
    review.interval = result.interval
    review.learningStage = result.learningStage
    review.lapseCount = result.lapseCount

    // 更新笔记的复习状态
    if updateRequest.difficulty == .ignored {
      note.reviewStatus = .completed  // 标记为已完成复习
    } else {
      note.reviewStatus = .scheduled
    }

    // 保存更改
    try await review.save(on: req.db)
    try await note.save(on: req.db)

    // 清除相关缓存
    let todayReviewsCacheKey = CacheConfig.KeyBuilder.todayReviews(userID: userID)
    try await req.cache.delete(forKey: todayReviewsCacheKey, on: req.redis)

    return BasicResponse(success: true, message: "笔记更新成功")
  }

  @Sendable
  func toggleSuspendStatus(req: Request) async throws -> NoteReview.ToggleSuspendResponse {
    let userID = try req.authenticatedUserID

    guard let noteIDString = req.parameters.get("noteID") else {
      throw Abort(.badRequest, reason: "无效的笔记ID参数")
    }

    guard let noteID = UUID(noteIDString) else {
      throw Abort(.badRequest, reason: "笔记ID格式无效")
    }

    // 验证笔记存在
    guard try await Note.find(noteID, on: req.db) != nil else {
      throw Abort(.notFound, reason: "笔记不存在")
    }

    // 查找复习记录
    guard
      let review = try await NoteReview.query(on: req.db)
        .filter(\.$note.$id == noteID)
        .filter(\.$user.$id == userID)
        .first()
    else {
      throw Abort(.notFound, reason: "未找到该笔记的复习记录")
    }

    // 切换暂停状态
    let isSuspended: Bool

    if review.learningStage == .suspended {
      // 如果当前是暂停状态，则恢复到适当的状态
      if review.interval > 0 {
        review.learningStage = .reviewing
      } else if review.reviewCount > 0 {
        review.learningStage = .learning
      } else {
        review.learningStage = .new
      }
      review.nextReviewDate = Date()  // 设置为立即可复习
      isSuspended = false
    } else {
      // 如果当前不是暂停状态，则设置为暂停
      review.learningStage = .suspended
      isSuspended = true
    }

    // 保存更改
    try await review.save(on: req.db)

    // 清除相关缓存
    let todayReviewsCacheKey = CacheConfig.KeyBuilder.todayReviews(userID: userID)
    try await req.cache.delete(forKey: todayReviewsCacheKey, on: req.redis)

    // 清除统计缓存
    let statsKey = CacheConfig.KeyBuilder.reviewStats(userID: userID)
    try await req.cache.delete(forKey: statsKey, on: req.redis)

    return NoteReview.ToggleSuspendResponse(
      success: true,
      message: isSuspended ? "已暂停笔记复习" : "已恢复笔记复习",
      suspended: isSuspended
    )
  }

  // 获取复习统计数据
  @Sendable
  func getReviewStats(req: Request) async throws -> NoteReview.ReviewStats {
    let userID = try req.authenticatedUserID

    // 尝试从缓存获取
    let cacheKey = CacheConfig.KeyBuilder.reviewStats(userID: userID)

    if let cachedStats = try await req.cache.get(
      forKey: cacheKey,
      as: NoteReview.ReviewStats.self,
      from: req.redis
    ) {
      return cachedStats
    }

    // 查询数据库
    let now = Date()
    let calendar = Calendar.mondayFirst

    // 1. 今日待复习数量
    let todayCount = try await NoteReview.query(on: req.db)
      .filter(\.$user.$id == userID)
      .filter(\.$nextReviewDate <= now)
      .filter(\.$difficulty != .ignored)
      .filter(\.$learningStage != .suspended)
      .count()

    // 2. 明日待复习数量
    let tomorrow = calendar.date(byAdding: .day, value: 1, to: now)!
    let tomorrowEnd = calendar.date(bySettingHour: 23, minute: 59, second: 59, of: tomorrow)!

    let tomorrowCount = try await NoteReview.query(on: req.db)
      .filter(\.$user.$id == userID)
      .filter(\.$nextReviewDate > now)
      .filter(\.$nextReviewDate <= tomorrowEnd)
      .filter(\.$difficulty != .ignored)
      .filter(\.$learningStage != .suspended)
      .count()

    // 3. 本周待复习数量
    let weekLater = calendar.date(byAdding: .day, value: 7, to: now)!

    let weekCount = try await NoteReview.query(on: req.db)
      .filter(\.$user.$id == userID)
      .filter(\.$nextReviewDate > tomorrowEnd)
      .filter(\.$nextReviewDate <= weekLater)
      .filter(\.$difficulty != .ignored)
      .filter(\.$learningStage != .suspended)
      .count()

    // 4. 总复习笔记数量
    let totalCount = try await NoteReview.query(on: req.db)
      .filter(\.$user.$id == userID)
      .count()

    // 5. 已完成复习的笔记数量
    let completedCount = try await NoteReview.query(on: req.db)
      .filter(\.$user.$id == userID)
      .filter(\.$difficulty == .ignored)
      .count()

    // 6. 学习中的笔记数量
    let learningCount = try await NoteReview.query(on: req.db)
      .filter(\.$user.$id == userID)
      .filter(\.$learningStage == .learning)
      .count()

    // 7. 重新学习中的笔记数量
    let relearningCount = try await NoteReview.query(on: req.db)
      .filter(\.$user.$id == userID)
      .filter(\.$learningStage == .relearning)
      .count()

    // 构建统计数据
    let stats = NoteReview.ReviewStats(
      todayReviewCount: todayCount,
      tomorrowReviewCount: tomorrowCount,
      weekReviewCount: weekCount,
      totalNoteCount: totalCount,
      completedNoteCount: completedCount,
      learningCount: learningCount,
      relearningCount: relearningCount
    )
    // 缓存统计数据
    try await req.cache.set(
      stats,
      forKey: cacheKey,
      duration: CacheConfig.Duration.ultraShort,
      type: .stat,
      on: req.redis
    )
    return stats
  }

  // 批量添加笔记到复习计划
  @Sendable
  func addNotesToReview(req: Request) async throws -> BasicResponse {
    let userID = try req.authenticatedUserID

    // 解析请求
    struct AddToReviewRequest: Content {
      let noteIDs: [UUID]
      let difficulty: NoteReview.Difficulty?
      let learningStage: NoteReview.LearningStage?
    }

    let addRequest = try req.content.decode(AddToReviewRequest.self)
    let difficulty = addRequest.difficulty ?? .good
    let learningStage = addRequest.learningStage ?? .new

    // 获取用户设置
    _ =
      try await UserReviewSettings.query(on: req.db)
      .filter(\.$user.$id == userID)
      .first()
      ?? UserReviewSettings(userID: userID)

    // 批量处理
    for noteID in addRequest.noteIDs {
      // 检查笔记是否存在且属于当前用户
      guard
        let note = try await Note.query(on: req.db)
          .filter(\.$id == noteID)
          .filter(\.$user.$id == userID)
          .first()
      else {
        continue  // 跳过不存在或不属于用户的笔记
      }

      // 检查是否已有复习记录
      let existingReview = try await NoteReview.query(on: req.db)
        .filter(\.$note.$id == noteID)
        .filter(\.$user.$id == userID)
        .first()

      if existingReview == nil {
        // 创建新的复习记录
        var nextReviewDate: Date?

        if learningStage == .new || learningStage == .learning {
          // 如果是新卡片或学习阶段，设置为现在
          nextReviewDate = Date()
        } else {
          // 如果是复习阶段，设置为明天
          nextReviewDate = Calendar.current.date(byAdding: .day, value: 1, to: Date())
        }

        let review = NoteReview(
          noteID: noteID,
          userID: userID,
          difficulty: difficulty,
          nextReviewDate: nextReviewDate,
          reviewCount: 0,
          easeFactor: 2.5,
          interval: 0,
          learningStage: learningStage,
          lapseCount: 0
        )
        try await review.save(on: req.db)

        // 更新笔记状态
        note.reviewStatus = .scheduled
        try await note.save(on: req.db)
      }
    }

    // 清除今日复习列表缓存
    let todayReviewsCacheKey = CacheConfig.KeyBuilder.todayReviews(userID: userID)
    try await req.cache.delete(forKey: todayReviewsCacheKey, on: req.redis)

    // 清除统计缓存
    let statsKey = CacheConfig.KeyBuilder.reviewStats(userID: userID)
    try await req.cache.delete(forKey: statsKey, on: req.redis)

    return BasicResponse(success: true, message: "已添加笔记到复习计划")
  }

  // 批量从复习计划中移除笔记
  @Sendable
  func removeNotesFromReview(req: Request) async throws -> BasicResponse {
    let userID = try req.authenticatedUserID

    struct RemoveFromReviewRequest: Content {
      let noteIDs: [UUID]
    }

    let removeRequest = try req.content.decode(RemoveFromReviewRequest.self)

    // 批量删除复习记录
    try await NoteReview.query(on: req.db)
      .filter(\.$user.$id == userID)
      .filter(\.$note.$id ~~ removeRequest.noteIDs)
      .delete()

    // 更新笔记状态
    for noteID in removeRequest.noteIDs {
      if let note = try await Note.find(noteID, on: req.db) {
        note.reviewStatus = .none
        try await note.save(on: req.db)
      }
    }

    // 清除今日复习列表缓存
    let todayReviewsCacheKey = CacheConfig.KeyBuilder.todayReviews(userID: userID)
    try await req.cache.delete(forKey: todayReviewsCacheKey, on: req.redis)

    // 清除统计缓存
    let statsKey = CacheConfig.KeyBuilder.reviewStats(userID: userID)
    try await req.cache.delete(forKey: statsKey, on: req.redis)

    return BasicResponse(success: true, message: "已从复习计划中移除笔记")
  }

  // 在 NoteReviewController 中添加新方法
  @Sendable
  func getReviewButtonStats(req: Request) async throws -> NoteReview.ReviewButtonStats {
    let userID = try req.authenticatedUserID

    // 解析请求参数（时间范围）
    enum TimeRange: String, Content {
      case month = "1个月"
      case quarter = "3个月"
      case year = "1年"
    }

    struct ReviewButtonStatsRequest: Content {
      let timeRange: TimeRange
    }

    let request = try req.query.decode(ReviewButtonStatsRequest.self)

    // 计算时间范围的起始日期
    let now = Date()
    let calendar = Calendar.mondayFirst
    let startDate: Date

    switch request.timeRange {
    case .month:
      startDate = calendar.date(byAdding: .month, value: -1, to: now)!
    case .quarter:
      startDate = calendar.date(byAdding: .month, value: -3, to: now)!
    case .year:
      startDate = calendar.date(byAdding: .year, value: -1, to: now)!
    }

    // 查询在指定时间范围内的复习记录
    let reviews = try await NoteReview.query(on: req.db)
      .filter(\.$user.$id == userID)
      .filter(\.$lastReviewDate >= startDate)
      .all()

    // 按学习阶段和难度分类统计
    var learning = [String: Int]()  // 学习中
    var needsWork = [String: Int]()  // 欠熟练
    var mastered = [String: Int]()  // 已熟练

    for review in reviews {
      let difficulty = review.difficulty.rawValue

      // 根据学习阶段和难度分类
      switch review.learningStage {
      case .new, .learning:
        learning[difficulty, default: 0] += 1
      case .relearning:
        needsWork[difficulty, default: 0] += 1
      case .reviewing:
        if review.interval < 7 {  // 小于7天的间隔视为欠熟练
          needsWork[difficulty, default: 0] += 1
        } else {
          mastered[difficulty, default: 0] += 1
        }
      case .suspended:
        // 可以单独统计或忽略
        break
      }
    }
    return NoteReview.ReviewButtonStats(
      learning: learning,
      needsWork: needsWork,
      mastered: mastered
    )
  }

  // 定义在类级别的预测类型枚举
  enum PredictionType: String, Content {
    case nextWeek = "next_week"  // 下周一到周日
    case nextMonth = "next_month"  // 下个月初到月末
    case customDays = "custom_days"  // 自定义天数
  }

  // 获取未来复习量预测
  @Sendable
  func getReviewPrediction(req: Request) async throws -> NoteReview.ReviewPrediction {
    let userID = try req.authenticatedUserID

    // 解析预测类型参数
    struct PredictionQuery: Content {
      var type: PredictionType?
      var days: Int?
    }

    let query = try req.query.decode(PredictionQuery.self)
    let predictionType = query.type ?? .nextWeek  // 默认为下周
    let customDays = query.days ?? 30  // 默认30天

    // 计算预测时间范围
    let calendar = Calendar.mondayFirst
    _ = calendar.startOfDay(for: Date())

    // 确定开始日期和天数
    let (startDate, daysCount) = try calculatePredictionRange(
      type: predictionType, customDays: customDays, calendar: calendar)

    // 尝试从缓存获取
    let cacheKey = CacheConfig.KeyBuilder.reviewPrediction(
      userID: userID,
      predictionType: predictionType.rawValue,
      customDays: customDays
    )

    if let cachedPrediction = try await req.cache.get(
      forKey: cacheKey,
      as: NoteReview.ReviewPrediction.self,
      from: req.redis
    ) {
      return cachedPrediction
    }

    // 预测指定时间范围的复习量
    let predictions = try await NoteReviewService.predictReviewVolume(
      userID: userID,
      db: req.db,
      startDate: startDate,
      timeRange: daysCount
    )

    let formatter = ISO8601DateFormatter()
    formatter.formatOptions = [.withFullDate]

    // 转换为字符串键的字典
    var predictionDict: [String: Int] = [:]
    var totalCount = 0

    for (date, count) in predictions {
      let dateString = formatter.string(from: date)
      predictionDict[dateString] = count
      totalCount += count
    }

    let response = NoteReview.ReviewPrediction(
      predictions: predictionDict,
      weeklyTotal: predictionType == .nextWeek ? totalCount : 0,
      monthlyTotal: predictionType == .nextMonth ? totalCount : 0,
      totalCards: totalCount
    )

    // 缓存预测结果（时效较短，因为用户可能会添加新笔记）
    try await req.cache.set(
      response,
      forKey: cacheKey,
      duration: CacheConfig.Duration.short,
      type: .noteReview,
      on: req.redis
    )

    return response
  }

  // 计算预测范围的辅助方法
  private func calculatePredictionRange(
    type: PredictionType,
    customDays: Int,
    calendar: Calendar
  ) throws -> (startDate: Date, daysCount: Int) {
    let today = calendar.startOfDay(for: Date())

    switch type {
    case .nextWeek:
      // 计算下周一的日期
      let weekday = calendar.component(.weekday, from: today)
      let daysUntilNextMonday = (9 - weekday) % 7  // 1是周一，到下周一的天数
      let nextMonday = calendar.date(
        byAdding: .day, value: daysUntilNextMonday == 0 ? 7 : daysUntilNextMonday, to: today)!
      return (nextMonday, 7)  // 下周一到周日，共7天

    case .nextMonth:
      // 计算下个月第一天
      var components = calendar.dateComponents([.year, .month], from: today)
      components.month! += 1
      components.day = 1

      let nextMonth = calendar.date(from: components)!

      // 计算下个月的天数
      let range = calendar.range(of: .day, in: .month, for: nextMonth)!
      let daysInNextMonth = range.count

      return (nextMonth, daysInNextMonth)  // 下个月第一天到月末

    case .customDays:
      if customDays <= 0 {
        return (today, 30)  // 默认30天
      }
      return (today, customDays)  // 从今天开始的自定义天数
    }
  }
}
